<?php
class Db {
	private $_aMasterConn = array ();
	private $_aSlaveConn = array ();
	private $_rResult;
	
	public function __construct() {
		self::parseConf ();
	}
	
	public function connect($aInfo, $sServer = 'db', $bMaster = true) {
		try {
			$sDsn = $aInfo ['type'] ? $aInfo ['type'] : 'mysql';
			if ($sDsn == 'sqlite') {
				$sDsn .= ':' . $aInfo ['name'];
			} else {
				$sDsn .= ':host=' . $aInfo ['host'] . ';dbname=' . $aInfo ['name'];
			}
			$oDbh = new PDO ( $sDsn, $aInfo ['user'], $aInfo ['password'] );
			$oDbh->setAttribute ( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
			if ($aInfo ['type'] == 'mysql' || $aInfo ['type'] == null) {
				$sCharset = $aInfo ['charset'] ? $aInfo ['charset'] : 'utf8';
				$oDbh->exec ( 'SET NAMES ' . $sCharset );
			}
			if ($bMaster) {
				$this->_aMasterConn [$sServer] = $oDbh;
			} else {
				$this->_aSlaveConn [$sServer] [] = $oDbh;
			}
		} catch ( Exception $e ) {
			$ee = new ExceptionExt ( $e->getMessage () );
			$ee->showError ();
		}
	}
	
	public function parseConf($sServer = 'db') {
		list ( $sServer, $sType ) = explode ( '_', $sServer );
		$aInfo = Base::getConf ( $sServer );
		if (is_array ( $aInfo )) {
			$aMatch = array ();
			if ($sServer == 'db') {
				$sPreg = '/^([a-z]+)_{0,1}(\d*)$/i';
			} else {
				if ($sType == 'cache') {
					$sPreg = '/^cache_([a-z]+)_{0,1}(\d*)$/i';
				} else {
					$sPreg = '/^db_([a-z]+)_{0,1}(\d*)$/i';
				}
			}
			foreach ( $aInfo as $sKey => $sRow ) {
				preg_match ( $sPreg, $sKey, $aMatch );
				if (count ( $aMatch )) {
					$aDbInfo [intval ( $aMatch [2] )] [$aMatch [1]] = $sRow;
				}
			}
			if (is_array ( $aDbInfo )) {
				sort ( $aDbInfo );
				$i = 0;
				foreach ( $aDbInfo as $aRow ) {
					if ($i == 0) {
						self::connect ( $aRow, $sServer );
					} else {
						self::connect ( $aRow, $sServer, false );
					}
				}
				return true;
			} else {
				return false;
			}
		}
		return false;
	}
	
	public function begin($sServer = 'db') {
		$oServer = self::_selectServer ( $sServer );
		$oServer->beginTransaction ();
	}
	
	public function commit($sServer = 'db') {
		$oServer = self::_selectServer ( $sServer );
		$oServer->commit ();
	}
	
	public function back($sServer = 'db') {
		$oServer = self::_selectServer ( $sServer );
		$oServer->rollBack ();
	}
	
	public function exec($sSql, $aValue = null, $sServer = 'db') {
		try {
			$oServer = self::_selectServer ( $sServer );
			$rResult = $oServer->prepare ( $sSql );
			if (is_array ( $aValue )) {
				$i = 1;
				foreach ( $aValue as $sKey => $val ) {
					if (is_int ( $sKey )) {
						$rResult->bindParam ( $i ++, $aValue [$sKey] );
					} else {
						$rResult->bindParam ( ':' . $sKey, $aValue [$sKey] );
					}
				}
			} elseif ($aValue !== null) {
				$rResult->bindParam ( 1, $aValue );
			}
			$rResult->execute ();
			return $rResult->rowCount ();
		} catch ( Exception $e ) {
			var_dump ( $e->getMessage () );
			$e->getMessage ();
		}
		return false;
	}
	
	public function query($sSql, $aValue = null, $sServer = 'db') {
		$this->_rResult = null;
		
		try {
			$oServer = self::_selectServer ( $sServer, false );
			$rResult = $oServer->prepare ( $sSql );
			if (is_array ( $aValue )) {
				$i = 1;
				foreach ( $aValue as $sKey => $val ) {
					if (is_int ( $sKey )) {
						$rResult->bindParam ( $i ++, $aValue [$sKey] );
					} else {
						$rResult->bindParam ( ':' . $sKey, $aValue [$sKey] );
					}
				}
			} elseif ($aValue !== null) {
				$rResult->bindParam ( 1, $aValue );
			}
			
			$rResult->execute ();
			$this->_rResult = $rResult;
			return $rResult;
		} catch ( Exception $e ) {
			echo $sSql;
			echo $e->getMessage ();
		}
		return false;
	}
	
	private function _selectServer($sServer, $bMaster = true, $bReload = false) {
		$iCount = count ( $this->_aSlaveConn [$sServer] );
		if ($iCount > 0 && $bMaster != true) {
			$i = floor ( rand ( 0, $iCount ) );
			return $this->_aSlaveConn [$sServer] [$i];
		} else {
			if (! $this->_aMasterConn [$sServer] && $bReload == false) {
				self::parseConf ( $sServer );
				self::_selectServer ( $sServer, $bMaster, true );
				return true;
			} else {
				if (! $this->_aMasterConn [$sServer]) {
					return false;
				}
				return $this->_aMasterConn [$sServer];
			}
		}
	}
	
	public function getRow($rResult = null, $sModel = 'assoc') {
		if ($rResult === null) {
			$rResult = $this->_rResult;
		}
		
		if ($rResult === null) {
			return false;
		}
		
		try {
			switch (strtolower ( $sModel )) {
				case 'assoc' :
					$sModel = PDO::FETCH_ASSOC;
					break;
				case 'num' :
					$sModel = PDO::FETCH_NUM;
					break;
				default :
					$sModel = PDO::FETCH_BOTH;
			}
			return $rResult->fetch ( $sModel );
		} catch ( Exception $e ) {
			$e->getMessage ();
		}
		
		return false;
	}
	
	public function getRows($rResult = null, $sModel = 'assoc') {
		if ($rResult === null) {
			$rResult = $this->_rResult;
		}
		
		if ($rResult === null) {
			return false;
		}
		
		try {
			switch (strtolower ( $sModel )) {
				case 'assoc' :
					$sModel = PDO::FETCH_ASSOC;
					break;
				case 'num' :
					$sModel = PDO::FETCH_NUM;
					break;
				default :
					$sModel = PDO::FETCH_BOTH;
			}
			return $rResult->fetchAll ( $sModel );
		} catch ( Exception $e ) {
			$e->getMessage ();
		}
		return false;
	}
	
	public function getResult($iIndex = 0, $rResult = null) {
		if ($rResult === null) {
			$rResult = $this->_rResult;
		}
		
		if ($rResult === null) {
			return false;
		}
		
		return $rResult->fetchColumn ( $iIndex );
	}
	
	public function getOne($sSql, $aValue = null, $sServer = 'db') {
		self::query ( $sSql, $aValue, $sServer );
		$aRow = self::getRow ( null, 'all' );
		if (count ( $aRow ) == 2) {
			return $aRow [0];
		} else {
			return $aRow;
		}
	}
	
	public function getList($sSql, $aValue, $iPage = 1, $iLimit = 30, $sServer = 'db') {
		$items = self::_getItems ( $sSql, $aValue, $sServer );
		$iPages = floor ( ($items - 1) / $iLimit ) + 1;
		if ($iPage == - 1 || $iPage > $iPages) {
			$iPage = $iPages;
		} 
		if (! $iPage) {
			$iPage = 1;
		}
		$sSql .= ' LIMIT ' . ($iPage - 1) * $iLimit . ', ' . $iLimit;
		self::query ( $sSql, $aValue, $sServer );
		return array ('items' => $items, 'pages' => $iPages, 'page' => $iPage, 'data' => self::getRows () );
	}
	
	private function _getItems($sSql, $aValue, $sServer) {
		$sSql = preg_replace ( '/[\r\n]+/', ' ', $sSql );
		$sSql = preg_replace ( '/(select).*(from.*)/iU', '$1 COUNT(1) $2', $sSql );
		$iPos = strripos ( $sSql, 'ORDER BY' );
		if ($iPos !== false) {
			$sSql = substr ( $sSql, 0, $iPos );
		}
		self::query ( $sSql, $aValue, $sServer );
		if (strripos ( $sSql, 'GROUP BY' )) {
			$rows = self::getRows ();
			return count ( $rows );
		} else {
			return self::getResult ();
		}
	}
	
	public function import($sSqlFile, $sServer = 'db') {
		$iVersion = self::getVersion ( $sServer );
		$rFp = fopen ( $sSqlFile, 'r' );
		$sSql = '';
		while ( ! feof ( $rFp ) ) {
			$sLine = trim ( fgets ( $rFp, 8000 ) );
			$aMatch = array ();
			
			if ($sLine == '') {
				continue;
			}
			
			if (preg_match ( '/^\-\-/', $sLine )) {
				continue;
			}
			
			preg_match ( '/\/\*\!(\d+)\s*(.+)\s*\*\/;/', $sLine, $aMatch );
			if (count ( $aMatch ) > 1) {
				if ($aMatch [1] <= $iVersion) {
					self::exec ( $aMatch [2], $sServer );
				}
				continue;
			}
			
			$iLen = mb_strlen ( $sLine, 'utf-8' );
			$sQuot = '';
			for($i = 0; $i < $iLen; $i ++) {
				$sChar = mb_substr ( $sLine, $i, 1, 'utf-8' );
				$sSql .= $sChar;
				if (($sChar == '\'' || $sChar == '"')) {
					if ($sChar != $sQuot) {
						$sQuot = $sChar;
					} else {
						$sQuot = '';
					}
				} elseif ($sQuot == '') {
					if ($sChar == ';') {
						self::exec ( $sSql, $sServer );
						$sSql = '';
					} elseif ($sChar == '\\') {
						$i ++;
						$sChar = mb_substr ( $sLine, $i, 1, 'utf-8' );
						$sSql .= $sChar;
					}
				}
			}
		}
	}
	
	public function getVersion($sServer = 'db') {
		$sSql = 'SELECT VERSION()';
		$sVersion = $this->getOne ( $sSql, $sServer );
		$aMatch = array ();
		preg_match ( '/^(\d+)\.(\d+)\.(\d+).*$/', $sVersion, $aMatch );
		return sprintf ( '%d%02d%02d', $aMatch [1], $aMatch [2], $aMatch [3] );
	}
}
?>